#pragma once
#ifndef q3map_hpp
#define q3map_hpp

#include <SDL/SDL_opengl.h>
#include <vector>
#include "camera.hpp"
#include "q3bsp.hpp"
#include "texture_loader.hpp"
#include "bsp_math.hpp"
#include "gl_extensions.hpp"

class BSP_BIQUADRATIC_PATCH
{
public:

	BSPvertex controlPoints[9];

	int tesselation;
	BSPvertex* vertices;
	GLuint * indices;

	//arrays for multi_draw_arrays
	int * trianglesPerRow;
	unsigned int ** rowIndexPointers;

	BSP_BIQUADRATIC_PATCH() : vertices(NULL)
	{}
	~BSP_BIQUADRATIC_PATCH()
	{
		if(vertices)
			delete [] vertices;
		vertices=NULL;

		if(indices)
			delete [] indices;
		indices=NULL;
	}

void Draw()
{
	//set array pointers
	glVertexPointer(3, GL_FLOAT, sizeof(BSPvertex), &vertices[0].vPos);

	glTexCoordPointer(2, GL_FLOAT, sizeof(BSPvertex), &vertices[0].vTexCoord);

	//glClientActiveTextureARB(GL_TEXTURE1_ARB);
	//glTexCoordPointer(2, GL_FLOAT, sizeof(BSPvertex), &vertices[0].vLightmapCoord);
	//glClientActiveTextureARB(GL_TEXTURE0_ARB);

		for(int row=0; row<tesselation; ++row)
		{
			glDrawElements(	GL_TRIANGLE_STRIP, 2*(tesselation+1), GL_UNSIGNED_INT,	&indices[row*2*(tesselation+1)]);
		}
/*glBegin(GL_LINES);
for(int i= 0;i< (tesselation+1)*(tesselation+1);i++){
 glVertex3fv(vertices[i].vPos);
}
glEnd();*/
}

 bool Tesselate(int newTesselation)
{
	tesselation=newTesselation;

	float px, py;
	BSPvertex temp[3];
	vertices=new BSPvertex[(tesselation+1)*(tesselation+1)];

	for(int v=0; v<=tesselation; ++v)
	{
		px=(float)v/tesselation;

		vertices[v]=controlPoints[0]*((1.0f-px)*(1.0f-px))+
					controlPoints[3]*((1.0f-px)*px*2)+
					controlPoints[6]*(px*px);
	}

	for(int u=1; u<=tesselation; ++u)
	{
		py=(float)u/tesselation;

		temp[0]=controlPoints[0]*((1.0f-py)*(1.0f-py))+
				controlPoints[1]*((1.0f-py)*py*2)+
				controlPoints[2]*(py*py);

		temp[1]=controlPoints[3]*((1.0f-py)*(1.0f-py))+
				controlPoints[4]*((1.0f-py)*py*2)+
				controlPoints[5]*(py*py);

		temp[2]=controlPoints[6]*((1.0f-py)*(1.0f-py))+
				controlPoints[7]*((1.0f-py)*py*2)+
				controlPoints[8]*(py*py);

		for(int v=0; v<=tesselation; ++v)
		{
			px=(float)v/tesselation;

			vertices[u*(tesselation+1)+v]=	temp[0]*((1.0f-px)*(1.0f-px))+
											temp[1]*((1.0f-px)*px*2)+
											temp[2]*(px*px);
//printf("%f %f %f\n",vertices[u*(tesselation+1)+v].vPos[0],vertices[u*(tesselation+1)+v].vPos[1],vertices[u*(tesselation+1)+v].vPos[2]);
		}
	}

	//Create indices
	indices=new GLuint[tesselation*(tesselation+1)*2];
	if(!indices)
	{
		printf("Unable to allocate memory for surface indices");
		return false;
	}

	for(int row=0; row<tesselation; ++row)
	{
		for(int point=0; point<=tesselation; ++point)
		{
			//calculate indices
			//reverse them to reverse winding
			indices[(row*(tesselation+1)+point)*2+1]=row*(tesselation+1)+point;
			indices[(row*(tesselation+1)+point)*2]=(row+1)*(tesselation+1)+point;
		}
	}


	//Fill in the arrays for multi_draw_arrays
	trianglesPerRow=new int[tesselation];
	rowIndexPointers=new unsigned int *[tesselation];
	if(!trianglesPerRow || !rowIndexPointers)
	{
		printf("Unable to allocate memory for indices for multi_draw_arrays");
		return false;
	}

	for(int row=0; row<tesselation; ++row)
	{
		trianglesPerRow[row]=2*(tesselation+1);
		rowIndexPointers[row]=&indices[row*2*(tesselation+1)];
	}

	return true;
}
};

//curved surface
class BSP_PATCH
{
public:
	int textureIndex;
	int lightmapIndex;
	int width, height;

	int numQuadraticPatches;
	BSP_BIQUADRATIC_PATCH * quadraticPatches;

 ~BSP_PATCH(){
  if(quadraticPatches){
   delete[] quadraticPatches;
  }
 }
};

class BSP_FACE_DIRECTORY_ENTRY
{
public:
	int faceType;
	int typeFaceNumber;		//face number in the list of faces of this type
};

class q3map{
private:
 int n_alreadyvis;
 int* alreadyvis;
 int n_visface;
 int i_visface;
 int* visface;

 int n_texture;
 GLuint* textures;

 int n_patches;
 BSP_PATCH* patches;

 int n_face_directory;
 BSP_FACE_DIRECTORY_ENTRY* faceDirectory;

 GLuint* vbo_face_vert;
 GLuint* vbo_face_tex;
 GLuint* vbo_face_light;
 GLuint* vbo_face_norm;
 GLuint* vbo_mesh_index;

public:
 q3bsp bsp;
 camera cam;

 q3map(){
  alreadyvis= NULL;
  visface= NULL;
  textures= NULL;
 }

 void destroy_map(){
  if(alreadyvis){
   free(alreadyvis);
  }
  if(visface){
   free(visface);
  }
  if(textures){
   glDeleteTextures(n_texture,textures);
   free(textures);
  }
  if(patches){
   delete[] patches;
  }
  if(faceDirectory){
   delete[] faceDirectory;
  }

  delete_VBO();
  bsp.destroy_bsp();
 }

void create_VBO(int n_verts){
 VECTOR3D vbo_vPos[n_verts];
 TEXCOORD vbo_vTexCoord[n_verts];
 TEXCOORD vbo_vLightmapCoord[n_verts];
 VECTOR3D vbo_vNormal[n_verts];
 GLuint* vbo_vIndex;
 for(int i= 0;i< bsp.n_vertex;i++){
  VECTOR_COPY(vbo_vPos[i],bsp.vertexes[i].vPos);
  VECTOR_COPY_2D(vbo_vTexCoord[i],bsp.vertexes[i].vTexCoord);
  VECTOR_COPY_2D(vbo_vLightmapCoord[i],bsp.vertexes[i].vLightmapCoord);
  VECTOR_COPY(vbo_vNormal[i],bsp.vertexes[i].vNormal);
 }
 //pos
 vbo_face_vert= NULL;
 vbo_face_vert= (GLuint*)malloc(sizeof(GLuint)*bsp.n_face);
 glGenBuffersARB(bsp.n_face,vbo_face_vert);
 for(int i= 0;i< bsp.n_face;i++){
  if(bsp.faces[i].type== FACE_POLY){
   glBindBufferARB(GL_ARRAY_BUFFER_ARB,vbo_face_vert[i]);
   glBufferDataARB(GL_ARRAY_BUFFER_ARB,sizeof(VECTOR3D)*bsp.faces[i].nVerts,&vbo_vPos[bsp.faces[i].vertIndex],GL_STATIC_DRAW_ARB);
  }
 }
 //texcoord
 vbo_face_tex= NULL;
 vbo_face_tex= (GLuint*)malloc(sizeof(GLuint)*bsp.n_face);
 glGenBuffersARB(bsp.n_face,vbo_face_tex);
 for(int i= 0;i< bsp.n_face;i++){
  if(bsp.faces[i].type== FACE_POLY){
   glBindBufferARB(GL_ARRAY_BUFFER_ARB,vbo_face_tex[i]);
   glBufferDataARB(GL_ARRAY_BUFFER_ARB,sizeof(TEXCOORD)*bsp.faces[i].nVerts,&vbo_vTexCoord[bsp.faces[i].vertIndex],GL_STATIC_DRAW_ARB);
  }
 }
 //lightmap
 vbo_face_light= NULL;
 vbo_face_light= (GLuint*)malloc(sizeof(GLuint)*bsp.n_face);
 glGenBuffersARB(bsp.n_face,vbo_face_light);
 for(int i= 0;i< bsp.n_face;i++){
  if(bsp.faces[i].type== FACE_POLY){
   glBindBufferARB(GL_ARRAY_BUFFER_ARB,vbo_face_light[i]);
   glBufferDataARB(GL_ARRAY_BUFFER_ARB,sizeof(TEXCOORD)*bsp.faces[i].nVerts,&vbo_vLightmapCoord[bsp.faces[i].vertIndex],GL_STATIC_DRAW_ARB);
  }
 }
 //normal
 vbo_face_norm= NULL;
 vbo_face_norm= (GLuint*)malloc(sizeof(GLuint)*bsp.n_face);
 glGenBuffersARB(bsp.n_face,vbo_face_norm);
 for(int i= 0;i< bsp.n_face;i++){
  if(bsp.faces[i].type== FACE_POLY){
   glBindBufferARB(GL_ARRAY_BUFFER_ARB,vbo_face_norm[i]);
   glBufferDataARB(GL_ARRAY_BUFFER_ARB,sizeof(VECTOR3D)*bsp.faces[i].nVerts,&vbo_vNormal[bsp.faces[i].vertIndex],GL_STATIC_DRAW_ARB);
  }
 }
 //vertindexek
 vbo_mesh_index= NULL;
 vbo_mesh_index= (GLuint*)malloc(sizeof(GLuint)*bsp.n_face);
 glGenBuffersARB(bsp.n_face,vbo_mesh_index);
 for(int i= 0;i< bsp.n_face;i++){
  if(bsp.faces[i].type== FACE_MESH){
   vbo_vIndex= (GLuint*)malloc(sizeof(GLuint)*bsp.faces[i].nMeshVerts);
   //printf("INDEX: ");
   for(int j= 0;j< bsp.faces[i].nMeshVerts;j++){
    vbo_vIndex[j]= bsp.meshverts[bsp.faces[i].meshVertIndex+j];
    //printf("%d ",vbo_vIndex[j]);
   }
   //printf("\n");
   glBindBufferARB(GL_ARRAY_BUFFER_ARB,vbo_face_vert[i]);
   glBufferDataARB(GL_ARRAY_BUFFER_ARB,sizeof(VECTOR3D)*bsp.faces[i].nMeshVerts,&vbo_vPos[bsp.faces[i].vertIndex],GL_STATIC_DRAW_ARB);
   glBindBufferARB(GL_ARRAY_BUFFER_ARB,vbo_face_tex[i]);
   glBufferDataARB(GL_ARRAY_BUFFER_ARB,sizeof(TEXCOORD)*bsp.faces[i].nMeshVerts,&vbo_vTexCoord[bsp.faces[i].vertIndex],GL_STATIC_DRAW_ARB);
   glBindBufferARB(GL_ARRAY_BUFFER_ARB,vbo_face_light[i]);
   glBufferDataARB(GL_ARRAY_BUFFER_ARB,sizeof(TEXCOORD)*bsp.faces[i].nMeshVerts,&vbo_vLightmapCoord[bsp.faces[i].vertIndex],GL_STATIC_DRAW_ARB);
   glBindBufferARB(GL_ARRAY_BUFFER_ARB,vbo_face_norm[i]);
   glBufferDataARB(GL_ARRAY_BUFFER_ARB,sizeof(VECTOR3D)*bsp.faces[i].nMeshVerts,&vbo_vNormal[bsp.faces[i].vertIndex],GL_STATIC_DRAW_ARB);
   glBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB,vbo_mesh_index[i]);
   glBufferDataARB(GL_ELEMENT_ARRAY_BUFFER_ARB,sizeof(int)*bsp.faces[i].nMeshVerts,vbo_vIndex,GL_STATIC_DRAW_ARB);
   free(vbo_vIndex);
  }
 }
}

void delete_VBO(void){
 if(vbo_face_vert){
  glDeleteBuffersARB(bsp.n_face,vbo_face_vert);
  free(vbo_face_vert);
 }
 if(vbo_face_tex){
  glDeleteBuffersARB(bsp.n_face,vbo_face_tex);
  free(vbo_face_tex);
 }
 if(vbo_face_light){
  glDeleteBuffersARB(bsp.n_face,vbo_face_light);
  free(vbo_face_light);
 }
 if(vbo_face_norm){
  glDeleteBuffersARB(bsp.n_face,vbo_face_norm);
  free(vbo_face_norm);
 }
 if(vbo_mesh_index){
  glDeleteBuffersARB(bsp.n_face,vbo_mesh_index);
  free(vbo_mesh_index);
 }
}

//
void DrawPatch(int patchNumber)
{
	//bind textures
	//unit 0 - decal texture
	glBindTexture(GL_TEXTURE_2D, textures[patches[patchNumber].textureIndex]);

	//unit 1 - lightmap
	//glActiveTextureARB(GL_TEXTURE1_ARB);
	//if(patches[patchNumber].lightmapIndex>=0)	//only bind a lightmap if one exists
		//glBindTexture(GL_TEXTURE_2D, lightmapTextures[patches[patchNumber].lightmapIndex]);
	//else
		//glBindTexture(GL_TEXTURE_2D, whiteTexture);
	//glActiveTextureARB(GL_TEXTURE0_ARB);

	for(int i=0; i<patches[patchNumber].numQuadraticPatches; ++i)
		patches[patchNumber].quadraticPatches[i].Draw();
}
//

 int load_bsp(char* bsp_file,int curveTesselation){
  int b= bsp.load_bsp_file(bsp_file);

  bsp.print_vertexes();

  n_alreadyvis= bsp.n_face;
  alreadyvis= (int*)malloc(n_alreadyvis*sizeof(int));

  n_visface= n_alreadyvis;
  visface= (int*)malloc(n_visface*sizeof(int));

  i_visface= n_visface;
  for(int i= 0;i< i_visface;i++){
   visface[i]= i;
  }

  n_texture= bsp.n_texture;

  n_face_directory= bsp.n_face;
  faceDirectory= new BSP_FACE_DIRECTORY_ENTRY[n_face_directory];

  n_patches= 0;
  for(int i= 0;i< bsp.n_face;i++){
   if(bsp.faces[i].type== FACE_PATCH){
    n_patches++;
   }
  }

  //Create space for BSP_PATCHes
	 patches=new BSP_PATCH[n_patches];

	 int currentPatch=0;
	 //convert loadFaces to patches
	 for(int i=0; i<bsp.n_face; ++i)
	 {
	 	if(bsp.faces[i].type!= FACE_PATCH){		//skip this loadFace if it is not a patch
	 		continue;
	 	}

		 patches[currentPatch].textureIndex=bsp.faces[i].textureID;
		 patches[currentPatch].lightmapIndex=bsp.faces[i].lightmapIndex;
		 patches[currentPatch].width=bsp.faces[i].size[0];
		 patches[currentPatch].height=bsp.faces[i].size[1];

		 //fill in this entry on the face directory
		 faceDirectory[i].faceType= FACE_PATCH;
		 faceDirectory[i].typeFaceNumber=currentPatch;

		 //Create space to hold quadratic patches
		 int numPatchesWide=(patches[currentPatch].width-1)/2;
		 int numPatchesHigh=(patches[currentPatch].height-1)/2;

		 patches[currentPatch].numQuadraticPatches=	numPatchesWide*numPatchesHigh;
		 patches[currentPatch].quadraticPatches=new BSP_BIQUADRATIC_PATCH
													[patches[currentPatch].numQuadraticPatches];
		 if(!patches[currentPatch].quadraticPatches)
		 {
		 	return false;
		 }

		 //fill in the quadratic patches
		 for(int y=0; y<numPatchesHigh; ++y)
		 {
		 	for(int x=0; x<numPatchesWide; ++x)
		 	{
		 		for(int row=0; row<3; ++row)
		 		{
		 			for(int point=0; point<3; ++point)
		 			{
		 				patches[currentPatch].quadraticPatches[y*numPatchesWide+x].
		 					controlPoints[row*3+point]=bsp.vertexes[bsp.faces[i].vertIndex+
		 						(y*2*patches[currentPatch].width+x*2)+
		 							row*patches[currentPatch].width+point];
		 			}
		 		}

			 	//tesselate the patch
			 	patches[currentPatch].quadraticPatches[y*numPatchesWide+x].Tesselate(curveTesselation);
			 }
		 }


		 ++currentPatch;
	 }

//VBO
  create_VBO(bsp.n_vertex);

  return b;
 }

 void load_textures(){
  textures= (GLuint*)malloc(sizeof(GLuint)*n_texture);
  for(int i= 0;i< n_texture;i++){
   load_texture(bsp.textures[i].texture_file,&textures[i]);
  }
 }

 void render_face(int f){
  if(bsp.faces[f].type== FACE_POLY){
   /*glBindTexture(GL_TEXTURE_2D,textures[bsp.faces[f].textureID]);
   glBegin(GL_TRIANGLE_FAN);
   for(int i= bsp.faces[f].vertIndex;i< bsp.faces[f].vertIndex+bsp.faces[f].nVerts;i++){
    glTexCoord2f(bsp.vertexes[i].vTexCoord[0],bsp.vertexes[i].vTexCoord[1]);
    glVertex3fv(bsp.vertexes[i].vPos);
   }
   glEnd();*/
   glBindTexture(GL_TEXTURE_2D,textures[bsp.faces[f].textureID]);
   glBindBufferARB(GL_ARRAY_BUFFER_ARB,vbo_face_vert[f]);
   glVertexPointer(3,GL_FLOAT,0,0);
   glBindBufferARB(GL_ARRAY_BUFFER_ARB,vbo_face_tex[f]);
   glTexCoordPointer(2,GL_FLOAT,0,0);
   glDrawArrays(GL_TRIANGLE_FAN,0,bsp.faces[f].nVerts);
  }
  if(bsp.faces[f].type== FACE_MESH){
   /*glBindTexture(GL_TEXTURE_2D,textures[bsp.faces[f].textureID]);
   glBegin(GL_TRIANGLES);
   for(int i= 0;i< bsp.faces[f].nMeshVerts;i++){
    glTexCoord2f(bsp.vertexes[bsp.faces[f].vertIndex+bsp.meshverts[bsp.faces[f].meshVertIndex+i]].vTexCoord[0],bsp.vertexes[bsp.faces[f].vertIndex+bsp.meshverts[bsp.faces[f].meshVertIndex+i]].vTexCoord[1]);
    glVertex3fv(bsp.vertexes[bsp.faces[f].vertIndex+bsp.meshverts[bsp.faces[f].meshVertIndex+i]].vPos);
   }
   glEnd();*/
   glBindTexture(GL_TEXTURE_2D,textures[bsp.faces[f].textureID]);
   glBindBufferARB(GL_ARRAY_BUFFER_ARB,vbo_face_vert[f]);
   glBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB,vbo_mesh_index[f]);
   glVertexPointer(3,GL_FLOAT,0,0);
   glBindBufferARB(GL_ARRAY_BUFFER_ARB,vbo_face_tex[f]);
   glTexCoordPointer(2,GL_FLOAT,0,0);
   glDrawElements(GL_TRIANGLES,bsp.faces[f].nMeshVerts,GL_UNSIGNED_INT,(GLvoid*)((GLuint*)NULL));
  }
  if(bsp.faces[f].type== FACE_PATCH){
   //glBindTexture(GL_TEXTURE_2D,textures[bsp.faces[f].textureID]);
   /*DrawPatch(faceDirectory[f].typeFaceNumber);*/
  }
 }

 void render_all_face(){
  for(int i= 0;i< bsp.n_face;i++){
   render_face(i);
  }
 }
};

#endif
